package com.ly.recommend;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author : 黑渊白花
 * @ClassName ScoreRecommend
 * @date : 2025/5/10 21:39
 * @Description
 */

public class ScoreRecommend {

    // 计算余弦相似度
    // 该方法用于计算两个用户之间的相似度，基于他们对相同帖子的评分进行计算
    // 余弦相似度的值越大，说明两个用户的兴趣越相似
    public static double cosineSimilarity(Map<Integer, Double> ratings1, Map<Integer, Double> ratings2) {
        // 获取两个用户的评分的共同帖子ID
        Set<Integer> commonBooks = new HashSet<>(ratings1.keySet());
        commonBooks.retainAll(ratings2.keySet());  // 求交集，找到共同评分的帖子

        // 如果两个用户没有共同评分的帖子，相似度为0
        if (commonBooks.isEmpty()) return 0;

        double dotProduct = 0.0;  // 点积
        double normA = 0.0;  // 向量A的模
        double normB = 0.0;  // 向量B的模

        // 计算点积和各自向量的模
        for (int actId : commonBooks) {
            double rating1 = ratings1.get(actId);  // 用户1对该帖子的评分
            double rating2 = ratings2.get(actId);  // 用户2对该帖子的评分
            dotProduct += rating1 * rating2;  // 计算点积
            normA += Math.pow(rating1, 2);  // 计算用户1评分的平方和
            normB += Math.pow(rating2, 2);  // 计算用户2评分的平方和
        }

        // 计算并返回余弦相似度
        return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
    }

    // 找到与目标用户最相似的三个用户
    // 该方法通过计算余弦相似度来找到与目标用户最相似的三个用户
    public static List<Integer> findTopThreeSimilarUsers(int targetUserId, Map<Integer, Map<Integer, Double>> userRatings) {
        // 使用优先队列（最大堆）存储用户相似度和用户ID
        PriorityQueue<Map.Entry<Integer, Double>> pq = new PriorityQueue<>(
                (e1, e2) -> Double.compare(e1.getValue(), e2.getValue())  // 按相似度升序排序
        );
        // 遍历所有用户，计算与目标用户的相似度
        for (Map.Entry<Integer, Map<Integer, Double>> entry : userRatings.entrySet()) {
            int userId = entry.getKey();  // 用户ID
            if (userId == targetUserId) continue;  // 跳过目标用户本身
            // 计算余弦相似度
            double similarity = cosineSimilarity(userRatings.get(targetUserId), entry.getValue());
            // 将用户和相似度存入优先队列
            Map.Entry<Integer, Double> user = new AbstractMap.SimpleEntry<>(userId, similarity);
            pq.offer(user);

            // 如果队列中的用户数超过3个，移除最不相似的用户
            if (pq.size() > 3) {
                pq.poll();
            }
        }

        // 从优先队列中提取出最相似的三个用户
        List<Integer> topUsers = new ArrayList<>();
        while (!pq.isEmpty()) {
            topUsers.add(pq.poll().getKey());  // 提取用户ID
        }
        return topUsers;  // 返回最相似的三个用户的ID
    }

    // 基于相似用户推荐帖子
    // 该方法基于与目标用户最相似的用户来推荐帖子
    public static List<Map.Entry<Integer, Double>> recommendStickOnSimilarUsers(Map<Integer, Map<Integer, Double>> userRatings,
                                                                                List<Integer> similarUsers, int targetUserId) {
        // 输入参数有效性检查
        if (userRatings == null || similarUsers == null) {
            return Collections.emptyList();
        }

        // 获取目标用户的评分记录
        Map<Integer, Double> targetUserRatings = userRatings.get(targetUserId);
        if (targetUserRatings == null) {
            return Collections.emptyList();
        }

        // 存储每个帖子的加权推荐得分
        Map<Integer, Double> actScores = new HashMap<>();

        // 遍历所有相似用户，计算推荐得分
        for (int userId : similarUsers) {
            Map<Integer, Double> similarUserRatings = userRatings.get(userId);
            if (similarUserRatings == null) continue;

            // 计算相似用户与目标用户的相似度
            double similarity = cosineSimilarity(targetUserRatings, similarUserRatings);

            for (Map.Entry<Integer, Double> entry : similarUserRatings.entrySet()) {
                int actId = entry.getKey();
                double rating = entry.getValue();

                // 如果目标用户没有评价过该帖子，则计算推荐得分
                if (!targetUserRatings.containsKey(actId)) {
                    // 加权计算：推荐得分 = 评分 * 相似度
                    actScores.merge(actId, rating * similarity, Double::sum);
                }
            }
        }

        // 将推荐得分进行排序，从高到低
        List<Map.Entry<Integer, Double>> sortedActs = new ArrayList<>(actScores.entrySet());
        sortedActs.sort(Map.Entry.<Integer, Double>comparingByValue().reversed());

        // 只返回前4个推荐的帖子
        return sortedActs.stream()
                .limit(4)
                .collect(Collectors.toList());
    }
}